package core;

import filemanager.FileTransferClientSocket;
import filemanager.ProgramRunner;
import graphic.ChatDialogPanel;
import graphic.FilesTabbedPanel;
import graphic.MainFrame;
import graphic.ReleasesPanel;
import graphic.UsersPanel;
import graphic.dialogs.ProgramConfigurationDialog;
import graphic.dialogs.ReleaseCommitDialog;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import javax.swing.JOptionPane;

import utility.Utility;
import dbms.DBCheckAndConfigurationFunctions;
import dbms.DBGeneralFunctions;

public class Client extends Thread {

	protected static Client CLIENT;
	
	public boolean manuallyClose;

	String userId;
	Socket client;
	ObjectOutputStream out;
	ObjectInputStream in;
	
	String serverIP;
	
	HashMap<Integer, String> configs;	
	HashMap<String, Program> associatedPrograms;
	HashMap<String, LinkedList<ProgramFile>> programFiles;
	
	String programNameLockRequest;
	String fileNameLockRequest;
	
	public Client()
	{
		this.associatedPrograms = new HashMap<String, Program>();
		this.programFiles = new HashMap<String, LinkedList<ProgramFile>>();
		
		this.manuallyClose = false;
		
		MainFrame.getInstance();
	}
	
	public boolean connect(String ip, int port) {
		
		try {
			this.client = new Socket(ip, port);
			this.out = new ObjectOutputStream(this.client.getOutputStream());
			this.out.flush();
			this.in = new ObjectInputStream(this.client.getInputStream());
			this.serverIP = ip;
			return true;
		} catch (UnknownHostException e) {
			
			return false;
		} catch (IOException e) {
			
			return false;
		}
	}

	public boolean login(String userid, String password)
	{
		Utility.sendSocketMessage(NetProtocoll.LOGIN + userid + ";" + password, this.out);
		
		if(Utility.readSocketMessage(this.in).equals(NetProtocoll.LOGINOK))
		{
			this.userId = userid;

			this.startClient();			
			return true;
		}
		else
			return false;
	}
	
	@Override
	public void run()
	{
		String message = Utility.readSocketMessage(this.in);
		
		while(!message.equals(NetProtocoll.BYE) && !message.equals(NetProtocoll.ERROR))
		{
			if(message.startsWith(NetProtocoll.STARTINIT))
				this.initProgramsAndFiles();
		
			if(message.equals(NetProtocoll.STARTUSERLIST))
				this.getUserList();
			
			if(message.equals(NetProtocoll.STARTRELEASELIST))
				this.getReleaseList();
			
			if(message.startsWith(NetProtocoll.LOCKINFO))
				this.getLockInfo(message);
			
			if(message.equals(NetProtocoll.FILELOCKOK))
				this.openFile();
			
			if(message.equals(NetProtocoll.FILELOCKFAILED))
				MainFrame.getInstance().showMessage("The File is Locked!", JOptionPane.WARNING_MESSAGE);
			
			if(message.equals(NetProtocoll.NEWFILEOK))
				MainFrame.getInstance().showMessage("New File Created!", JOptionPane.INFORMATION_MESSAGE);
			
			if(message.equals(NetProtocoll.NEWFILEFAILED))
				MainFrame.getInstance().showMessage("New NOT File Created!", JOptionPane.WARNING_MESSAGE);

			if(message.startsWith(NetProtocoll.CHAT))
				this.getChatMessage(message);
			
			if(message.startsWith(NetProtocoll.CLIENTLOGGEDIN))
				this.getLoggedClientInfo(message, true);
			
			if(message.startsWith(NetProtocoll.CLIENTLOGGEDOUT))
				this.getLoggedClientInfo(message, false);
			
			message = Utility.readSocketMessage(this.in);
		}
		
		try {
			this.client.close();
		} catch (IOException e) {}
		
		if(!this.manuallyClose)
		{
			if(message.equals(NetProtocoll.ERROR))
				MainFrame.getInstance().showMessage("The connection with the server is dead!", JOptionPane.ERROR_MESSAGE);
		
			if(message.equals(NetProtocoll.BYE))
				MainFrame.getInstance().showMessage("The server closed the connection!", JOptionPane.ERROR_MESSAGE);
		}
		
		MainFrame.getInstance().close();		
	}
	
	protected void getLoggedClientInfo(String message, boolean loggedIn) {
		String userId;
		if(loggedIn)
		{
			userId = message.substring(NetProtocoll.CLIENTLOGGEDIN.length());
			UsersPanel.getInstance().addUser(userId);
		} else
		{
			userId = message.substring(NetProtocoll.CLIENTLOGGEDOUT.length());
			UsersPanel.getInstance().removeUser(userId);
		}
	}

	// Bisogna aver già ricevuto il msg STARTINIT
	protected void initProgramsAndFiles()
	{
		String message = Utility.readSocketMessage(this.in);
		
		ProgramFile file;
		LinkedList<ProgramFile> programFileList;
		
		while(!message.equals(NetProtocoll.ENDINIT) && !message.equals(NetProtocoll.ERROR))
		{
			String []parameters = message.substring(NetProtocoll.INITPROGRAM.length()).split(";");
			programFileList = new LinkedList<ProgramFile>();
			this.associatedPrograms.put(parameters[0], new Program(parameters[0], parameters[1], parameters[2].equals(Program.YES)));
			this.programFiles.put(parameters[0], programFileList);

			message = Utility.readSocketMessage(this.in);
			
			while(message.startsWith(NetProtocoll.INITFILE))
			{
				String []fileParametes = message.substring(NetProtocoll.INITFILE.length()).split(";");

				file = new ProgramFile(Integer.parseInt(fileParametes[0]), fileParametes[1]);
				file.setLast_edit_by(fileParametes[2]);
				file.setLast_edit_when(fileParametes[3]);
				file.setRelease_number(Integer.parseInt(fileParametes[4]));
				
				programFileList.add(file);
				
				message = Utility.readSocketMessage(this.in);
			}
		}
		
		MainFrame.getInstance().paintClientPanel();	
	}
	
	public void requestUserList()
	{
		Utility.sendSocketMessage(NetProtocoll.USERLIST, this.out);
	} 
	
	public void getUserList()
	{
		UsersPanel.getInstance().cleanTable();
		
		String message = Utility.readSocketMessage(this.in);
		
		while(!message.equals(NetProtocoll.ENDUSERLIST) && !message.equals(NetProtocoll.ERROR))
		{
			String userId = message.substring(NetProtocoll.USER.length());
			UsersPanel.getInstance().addUser(userId);
			
			message = Utility.readSocketMessage(this.in);
		}
	}
	
	public void requestReleaseList(int fileId)
	{
		Utility.sendSocketMessage(NetProtocoll.RELEASELIST + String.valueOf(fileId), this.out);
	}
	
	protected void getReleaseList()
	{
		LinkedList<ProgramFileRelease> releases = new LinkedList<ProgramFileRelease>();
		
		String message = Utility.readSocketMessage(this.in);
		
		ProgramFileRelease fileRelease;
		while(!message.equals(NetProtocoll.ENDRELEASELIST))
		{
			String []parameter = message.substring(NetProtocoll.RELEASE.length()).split(";");

			fileRelease = new ProgramFileRelease();
			fileRelease.setFileId(Integer.parseInt(parameter[0]));
			fileRelease.setAdded_by(parameter[1]);
			fileRelease.setAdded_when(Timestamp.valueOf(parameter[2]));
			fileRelease.setNumber(Integer.parseInt(parameter[3]));
			fileRelease.setNote(parameter[4]);
			
			releases.add(fileRelease);
			
			message = Utility.readSocketMessage(this.in);
		}
		
		ReleasesPanel.getInstance().refreshTable(releases);
	}
	
	public void getLockInfo(String message)
	{
		String []parameters = message.substring(NetProtocoll.LOCKINFO.length()).split(";");

		LinkedList<ProgramFile> programFiles = this.programFiles.get(parameters[0]);
		for (Iterator iterator = programFiles.iterator(); iterator.hasNext();) {
			ProgramFile programFile = (ProgramFile) iterator.next();
			if(programFile.fileName.equals(parameters[1]))
			{
				if(parameters[2].equals(NetProtocoll.ADD_LOCK))
					programFile.lockFile(parameters[3]);
				else
					programFile.unLockFile();				
			}
		}
		
		FilesTabbedPanel.getIstance().getTabs().get(parameters[0]).refreshTable();
	}
	
	public synchronized void requestFileLock(boolean release)
	{
		this.programNameLockRequest = FilesTabbedPanel.getIstance().getSelectedProgramName();
		this.fileNameLockRequest = FilesTabbedPanel.getIstance().getTabs().get(this.programNameLockRequest).getSelectedFileName();
		
		if(this.programNameLockRequest == null)
		{
			MainFrame.getInstance().showMessage("No Program has been selected!", JOptionPane.WARNING_MESSAGE);
			return;
		}
		
		if(this.fileNameLockRequest == null)
		{
			MainFrame.getInstance().showMessage("No File or Release has been selected!", JOptionPane.WARNING_MESSAGE);
			return;
		}
		
		if(!DBCheckAndConfigurationFunctions.isProgramConfigured(this.programNameLockRequest))
			new ProgramConfigurationDialog();
		else
		{
			String msg;
			if(!release)
				msg = NetProtocoll.REQUESTFILELOCK + this.programNameLockRequest + ";" + this.fileNameLockRequest + ";-1";
			else
			{
				int releeaseNumber = ReleasesPanel.getInstance().getSelectedReleaseNumber();
				msg = NetProtocoll.REQUESTFILELOCK + this.programNameLockRequest + ";" + this.fileNameLockRequest + ";" + releeaseNumber;
			}
			Utility.sendSocketMessage(msg, this.out);
		}
	}
	
	public synchronized void openFile()
	{
		try {
			
			FileTransferClientSocket fileTransferClientSocket = new FileTransferClientSocket();
			
			while(!fileTransferClientSocket.isReady())
				try {
					this.sleep(101);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			
			Utility.sendSocketMessage(NetProtocoll.READYTORECEIVE, this.out);
			// TODO: Attenzione potrebbe essere avviata dopo l'invio del messaggio da parte del client..
			
			
			if(!fileTransferClientSocket.getFile())
			{
				MainFrame.getInstance().showMessage("Error while getting the file from the server..", JOptionPane.ERROR_MESSAGE);
				return;
			}
			
			String extenzion = this.getProgramFromAssociatedOnes(this.programNameLockRequest).getExtenzion();
			
			ProgramRunner runner = new ProgramRunner(this.programNameLockRequest , this.fileNameLockRequest , extenzion);
			runner.start();
			
			// Il rilascio del lock viene invocato dal runner
			
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}		
	}

	public void closeFile(String programName, String fileName, String extenzion)
	{
		String msg = NetProtocoll.RELEASEFILELOCK;
		msg += programName + ";";
		msg += fileName;
		
		Utility.sendSocketMessage(msg, this.out);
		
		/* Check if the client wants to committ a new release or not: */
		ReleaseCommitDialog dialog = new ReleaseCommitDialog();

		while(!dialog.isCommittVarSet())
			try {
				this.sleep(101);
			} catch (InterruptedException e1) {}
			
		if(!dialog.isCommitt())
		{
			Utility.sendSocketMessage(NetProtocoll.NOCOMMITFILE, this.out);
			return;
		}
		
		Utility.sendSocketMessage(NetProtocoll.COMMITFILE + dialog.getNote(), this.out);	
		
		FileTransferClientSocket fileTransferClientSocket = new FileTransferClientSocket();
		
		while(!fileTransferClientSocket.isReady())
			try {
				this.sleep(101);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
		if(fileTransferClientSocket.sendFile(programName, fileName, extenzion))
			MainFrame.getInstance().showMessage("File committed!", JOptionPane.INFORMATION_MESSAGE);
		else
			MainFrame.getInstance().showMessage("Error while committing the file!", JOptionPane.ERROR_MESSAGE);
	}
	
	public Program getProgramFromAssociatedOnes(String programName)
	{
		for (Iterator iterator = this.getAssociatedPrograms().keySet().iterator(); iterator.hasNext();)
		{
			String iteratorProgramName = (String) iterator.next();
			if(iteratorProgramName.equals(programName))
				return this.getAssociatedPrograms().get(iteratorProgramName);
		}
				
		return null;
	}
	
	public void newFileCreationRequest(String programName, String newFileName)
	{
		Utility.sendSocketMessage(NetProtocoll.NEWFILE + programName + ";" + newFileName, this.out);
	}
	
	public void sendChatMessage(String receiver, String chatMessage)
	{
		String message = NetProtocoll.CHAT;
		message += receiver + ";";
		message += chatMessage;
		
		ChatDialogPanel.getIstance().writeChatMessage(receiver, this.userId, chatMessage);

		Utility.sendSocketMessage(message, this.out);
	}
	
	public void getChatMessage(String message)
	{
		String []parameters = message.substring(NetProtocoll.CHAT.length()).split(";", 2);
		
		ChatDialogPanel.getIstance().writeChatMessage(parameters[0], parameters[0], parameters[1]);
	}
	
	public void startClient()
	{
		this.configs = DBGeneralFunctions.getClientConfigurationMap();
		this.start();
	}
	
	public void stopClient()
	{
		try {
			this.client.close();
		} catch (IOException e) {}
	}
	
	public static Client getInstance()
	{
		if(CLIENT == null) CLIENT = new Client();
		return CLIENT;
	}
	
	public String getUserId() {
		return userId;
	}
	
	public String getServerIP() {
		return serverIP;
	}
	
	public HashMap<Integer, String> getConfigs() {
		return configs;
	}

	public HashMap<String, Program> getAssociatedPrograms() {
		return associatedPrograms;
	}

	public HashMap<String, LinkedList<ProgramFile>> getProgramFiles() {
		return programFiles;
	}

	public static void main(String[] args)
	{
		Client.getInstance();	
	}
}